# -*- coding:utf-8 -*-

import tornado.web
import tornado.websocket
import tornado.log
import tornado.gen
import tornado.escape
import json
import time
from typing import Union, Dict, Any, List

from .. import config as svr_conf
from service.utility import encryption, mcron
from .. import exception
from ..utility import db_data_tool, data_tool


class BaseRequestHandler(tornado.web.RequestHandler):
    mdb = svr_conf.mdb
    data_tool = data_tool
    db_data_tool = db_data_tool
    # case_cache = mcache.case_cache
    # element_cache = mcache.element_cache
    config = svr_conf
    static_path = svr_conf.static_path
    sched = mcron.sched
    encryption = encryption
    exception = exception
    # executor = config.executor_pool

    @property
    def current_user_id(self):
        if not hasattr(self, '_current_user_id'):
            self._current_user_id = self._get_current_user_id()
        return self._current_user_id

    def _get_current_user_id(self):
        uid = self.user_info.get('id')
        if not uid:
            raise Exception('获取当前用户ID失败！')
        return uid

    def get_current_user(self):
        token = self.request.headers.get("token", '')
        if not token:
            token = self.get_cookie('token', '')
        if not token:
            token = self.get_cookie('tdp_token', '')
        return token

    async def _get_current_user_info(self):
        # if sid:
        #     self.user_info = decode(token)
        self.user_info = await self.mdb.query_one(
            'select id, user_name, real_name, create_time, mark from tdp_user where token=%s and token_expire_time>now()',
            self.user_token
        )
        if self.user_info:
            await self.mdb.execute(
                'UPDATE tdp_user SET token_expire_time=date_add(now(), INTERVAL 5 hour) WHERE id=%s',
                self.user_info['id']
            )

    async def verity_user(self):
        await self._get_current_user_info()
        slave_id = self.request.headers.get('sid')
        if not self.user_info:  # 用户登陆判断
            if self.sso_token:
                return True
            elif self.request.uri == '/':
                self.redirect('/static/index.html')
            elif 'test/trigger/ck_env' in self.request.uri:
                return True
            # 接口性能测试使用的测试接口
            elif self.request.uri.startswith('/tdp/interface/test?'):
                return True
            # elif self.request.path.startswith('/autotest/extrigger'):
            #     return True
            else:
                if self.request.uri == '/user/login' or self.request.uri.startswith('/user/login?') \
                        or self.request.uri.startswith('/static/login.html') \
                        or self.request.uri.startswith('/static/tv2/index.html'):
                    return True
                elif slave_id:  # 执行机注册
                    if self.request.headers.get('sid') != slave_id:
                        self.finish('register error!')
                        return False
                    else:
                        return True
                else:
                    # self.finish(json.dumps({'code': 9999, 'msg': '请登陆！'}, ensure_ascii=False))
                    self.redirect('/')
            self.app_logger.info('访问鉴权失败！')
            return False
        return True

    def json_default(self, obj):
        if isinstance(obj, bytes):
            return obj.decode()
        return obj

    def options(self, *args, **kwargs):
        pass

    def initialize(self, **kwargs):
        super(BaseRequestHandler, self).initialize()
        request_origin = self.request.headers.get('Origin', '*')
        self.set_header("Access-Control-Allow-Credentials", "true")
        self.set_header("Access-Control-Allow-Origin", request_origin)  # 这个地方可以写域名
        self.set_header("Access-Control-Allow-Headers", "x-requested-with, token, content-type")
        self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT')
        self.set_header("Access-Control-Max-Age",
                        "3600")  # 用来指定本次预检请求的有效期，单位为秒，，在此期间不用发出另一条预检请求。 #定义一个响应OPTIONS 请求，不用作任务处理

        j = self.request.headers.get('Content-Type', '')
        self.app_logger = tornado.log.app_log
        self.request_json = {}
        self.success_msg = '成功'
        self.success_code = 0
        self.success_data = {}
        self.exception_data = {}
        self.exception_code = 4
        self.exception_msg = ''
        self.user_info = {}
        self.user_token = self.current_user
        self.sso_token = self.get_argument('sso_token', '')

        if 'application/json' in j and self.request.body:
            try:
                self.request_json = json.loads(self.request.body)
            except:
                tornado.log.app_log.exception(f'请求体json解析错误: {self.request.body}')

        tornado.log.access_log.info(
            f'accept: {self.request.method} {self.request.uri}，'
            f'header: {dict(self.request.headers)},'
            f'body_arguments: {json.dumps(self.request.body_arguments, default=self.json_default, ensure_ascii=False)},'
            f'request_json: {self.request_json}'
        )

    def now_strftime(self):
        return time.strftime('%Y-%m-%d %H:%M:%S')

    def write_static(self, file, encoding="utf-8", *args):
        self.set_header('Content-Type', f'text/html;charset={encoding}')
        with open(file, encoding=encoding, *args) as f:
            self.write(f.read())

    def write_str(self, s, encoding="utf-8"):
        self.set_header('Content-Type', f'text/plain;charset={encoding}')
        self.write(s)

    def resp_success(self, code=0):
        self.set_header('Content-Type', 'application/json;charset=utf-8')
        try:
            self.finish(
                json.dumps(
                    {
                        'code': code,
                        'msg': self.success_msg,
                        'data': self.success_data
                    }, ensure_ascii=False
                )
            )
        except RuntimeError as e:
            if 'finish() called twice' not in repr(e):
                raise

    def resp_error(self):
        self.set_header('Content-Type', 'application/json;charset=utf-8')
        try:
            self.finish(
                json.dumps(
                    {
                        'code': self.exception_code,
                        'msg': self.exception_msg,
                        'data': self.exception_data
                    }, ensure_ascii=False
                )
            )
        except RuntimeError as e:
            if 'finish() called twice' not in repr(e):
                raise

    async def after_request(self):
        pass

    async def call_method(self, obj, method_name, *args, **kwargs):
        if not (await self.verity_user()):
            return
        # 校验用户请求权限
        s = (await self.mdb.query_one(
            "select `value` from test_config where `name`='user_permission_query'"))['value']
        user_name = self.user_info.get('user_name')
        if s and user_name:
            permission_query_users = json.loads(s)
            if user_name in permission_query_users and self.request.method != 'GET'\
                    and self.request.uri != '/user/login':
                self.set_status(403)
                self.finish('您没有此操作权限！')
                return
        try:
            f = getattr(obj, method_name)
        except AttributeError:
            self.set_status(400)
            self.finish("请求资源或方法有误！")
            raise
        try:
            await f(*args, **kwargs)
            self.resp_success(self.success_code)
            await self.after_request()
        except Exception as e:
            if self.exception_msg:
                self.resp_error()
                return
            try:
                d = exception.exception(e)
                self.exception_code = d['code']
                self.exception_msg = d['msg']
                self.resp_error()
            except Exception as e1:
                self.exception_code = 9
                self.exception_msg = f'异常捕获发生错误!\n{repr(e1)}'
                self.resp_error()
                tornado.log.app_log.exception(str(e1))
            tornado.log.app_log.exception(str(e))

    async def get(self, *args, **kwargs):
        resource_name = self.get_argument('c', 'default')
        await self.call_method(self, f'get_{resource_name}', *args, **kwargs)

    async def get_default(self, *args, **kwargs):
        raise NotImplementedError()

    async def post(self, *args, **kwargs):
        resource_name = self.get_argument('c', 'default')
        await self.call_method(self, f'post_{resource_name}', *args, **kwargs)

    async def post_default(self, *args, **kwargs):
        raise NotImplementedError

    async def delete(self, *args, **kwargs):
        resource_name = self.get_argument('c', 'default')
        await self.call_method(self, f'delete_{resource_name}', *args, **kwargs)

    async def delete_default(self, *args, **kwargs):
        raise NotImplementedError

    async def put(self, *args, **kwargs):
        resource_name = self.get_argument('c', 'default')
        await self.call_method(self, f'put_{resource_name}', *args, **kwargs)

    async def put_default(self, *args, **kwargs):
        raise NotImplementedError

    async def patch(self, *args, **kwargs):
        resource_name = self.get_argument('c', 'default')
        await self.call_method(self, f'patch_{resource_name}', *args, **kwargs)

    async def patch_default(self, *args, **kwargs):
        raise NotImplementedError

    def escape_string(self, s):
        return tornado.escape.xhtml_escape(s)

    def get_escaped_argument(self, key, default=None, strip=True):
        if default is not None:
            return self.escape_string(self.get_argument(key, default, strip=strip))
        else:
            return self.escape_string(self.get_argument(key))


class BaseWebsocketHandler(tornado.websocket.WebSocketHandler):
    mdb = svr_conf.mdb
    encryption = encryption
    exception = exception.exception
    exception_info = ''

    def call_slave_method(self, sid, method: str, data: dict={}):
        self.write_message({'method': method, 'data': data})
        from .tdp_slave_handler import TdpSlaveWebsocketHandler
        TdpSlaveWebsocketHandler.slave[sid]['last_letter_time'] = time.time()

    def check_origin(self, str):
        return True

    async def open(self):
        tornado.log.access_log.info(
            f'websocket open. {self.request.headers.get("X-Real-Ip", self.request.remote_ip)} {self.request.uri}\nheaders: {dict(self.request.headers)}')
        sid = self.request.headers.get('Sid', '')
        if sid:
            status = (await self.mdb.query_one(
                'select status from tdp_slave where id=%s', sid
            )).get('status')
            if not status:
                self.write_message('当前slave已被服务端删除，请删除程序目录下的.tdp文件后重新启动！')
                self.close()
                return False
            if status == 'on-line':
                self.write_message(json.dumps({'msg': '当前执行机处于在线状态，请勿重复连接！'}, ensure_ascii=False))
                raise Exception('已在线')
        action = self.request.headers.get('Open_action', None)
        if action:
            await getattr(self, action)(**self.request.headers)

    def write_message(self, message: Union[bytes, str, Dict[str, Any]], binary: bool = False):
        if isinstance(message, dict):
            s = json.dumps(message, ensure_ascii=False)
        else:
            s = message
        super(BaseWebsocketHandler, self).write_message(s, binary)

    async def on_message(self, message: str or bytes):
        tornado.log.access_log.info(
            f'websocket on message. {self.request.remote_ip} {self.request.uri}\nmsg: {message}\nheaders: {dict(self.request.headers)}')
        data = json.loads(message)
        try:
            action = data.get('action')
            await getattr(self, action)()
        except Exception as e:
            self.write_message({
                'action': f're_{action}',
                'msg': repr(e)
            })

    @tornado.gen.coroutine
    def on_close(self):
        tornado.log.access_log.info(
            f'websocket on close. {self.request.remote_ip} {self.request.uri}\nheaders: {dict(self.request.headers)}')
        sid = self.request.headers.get('Sid')
        if sid:
            from .tdp_slave_handler import TdpSlaveWebsocketHandler
            try:
                TdpSlaveWebsocketHandler.slave.pop(sid)
            except KeyError:
                pass
            yield self.mdb.update_by_dict(
                table='tdp_slave', colv={'status': 'off-line', 'parallel_now': 0}, where={'id': sid})



